"
]
},
{
"cell_type": "code",
"source": [
"!pip install fancyimpute -q\n",
"!pip install thefuzz -q\n",
"!pip install --upgrade xlrd -q\n",
"!pip install category_encoders -q"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "-BFRBxSXe8Bw",
"outputId": "4d104b88-a30a-4749-a54d-3e22420b2a45"
},
"execution_count": 1,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"\u001b[?25l\r\u001b[K |██▏ | 10 kB 14.6 MB/s eta 0:00:01\r\u001b[K |████▎ | 20 kB 18.1 MB/s eta 0:00:01\r\u001b[K |██████▍ | 30 kB 21.3 MB/s eta 0:00:01\r\u001b[K |████████▌ | 40 kB 23.9 MB/s eta 0:00:01\r\u001b[K |██████████▋ | 51 kB 6.5 MB/s eta 0:00:01\r\u001b[K |████████████▊ | 61 kB 7.6 MB/s eta 0:00:01\r\u001b[K |██████████████▉ | 71 kB 6.0 MB/s eta 0:00:01\r\u001b[K |█████████████████ | 81 kB 6.6 MB/s eta 0:00:01\r\u001b[K |███████████████████ | 92 kB 7.4 MB/s eta 0:00:01\r\u001b[K |█████████████████████▏ | 102 kB 8.0 MB/s eta 0:00:01\r\u001b[K |███████████████████████▎ | 112 kB 8.0 MB/s eta 0:00:01\r\u001b[K |█████████████████████████▍ | 122 kB 8.0 MB/s eta 0:00:01\r\u001b[K |███████████████████████████▌ | 133 kB 8.0 MB/s eta 0:00:01\r\u001b[K |█████████████████████████████▋ | 143 kB 8.0 MB/s eta 0:00:01\r\u001b[K |███████████████████████████████▊| 153 kB 8.0 MB/s eta 0:00:01\r\u001b[K |████████████████████████████████| 154 kB 8.0 MB/s \n",
"\u001b[?25h Building wheel for fancyimpute (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
" Building wheel for knnimpute (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
"\u001b[K |████████████████████████████████| 96 kB 2.6 MB/s \n",
"\u001b[K |████████████████████████████████| 86 kB 2.9 MB/s \n",
"\u001b[?25h"
]
}
]
},
{
"cell_type": "code",
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import seaborn as sns\n",
"\n",
"from sklearn.impute import SimpleImputer\n",
"from sklearn.ensemble import ExtraTreesRegressor\n",
"from sklearn.experimental import enable_iterative_imputer\n",
"from sklearn.impute import IterativeImputer\n",
"from sklearn.impute import KNNImputer\n",
"from sklearn.ensemble import RandomForestRegressor\n",
"from sklearn.model_selection import cross_val_score\n",
"from sklearn.preprocessing import StandardScaler\n",
"from sklearn.preprocessing import MinMaxScaler\n",
"from sklearn.preprocessing import PowerTransformer\n",
"from sklearn.preprocessing import QuantileTransformer\n",
"from sklearn.preprocessing import OneHotEncoder\n",
"from sklearn.preprocessing import OrdinalEncoder\n",
"from sklearn.model_selection import GroupKFold, train_test_split\n",
"\n",
"from fancyimpute import SoftImpute\n",
"\n",
"# helpful character encoding module\n",
"import chardet\n",
"from thefuzz import fuzz\n",
"from thefuzz import process\n",
"\n",
"from category_encoders import MEstimateEncoder\n",
"from category_encoders.wrapper import NestedCVWrapper\n",
"\n",
"import matplotlib as mpl\n",
"from matplotlib import pyplot as plt\n",
"%matplotlib inline"
],
"metadata": {
"id": "5bV_HvPiH-9i"
},
"execution_count": 3,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Handling missing value"
],
"metadata": {
"id": "6sb3RBiSo4Vf"
}
},
{
"cell_type": "markdown",
"source": [
"In this course, you'll learn why you've run into the data cleaning problems and, more importantly, how to fix them! In this section, you’ll learn how to tackle some of the most common data cleaning problems so you can get to actually analyzing your data faster. "
],
"metadata": {
"id": "qvr5MUX8pFwO"
}
},
{
"cell_type": "markdown",
"source": [
"### Take a first look at the data\n",
"\n",
"For demonstration, we'll use a dataset of events that occured in American Football games. You'll apply your new skills to a dataset of building permits issued in San Francisco."
],
"metadata": {
"id": "OvNovWe2Hobw"
}
},
{
"cell_type": "code",
"source": [
"# Upload the API’s key JSON file to your Colab\n",
"# session by running the following code in a notebook cell:\n",
"from google.colab import files\n",
"files.upload()"
],
"metadata": {
"colab": {
"resources": {
"http://localhost:8080/nbextensions/google.colab/files.js": {
"data": "Ly8gQ29weXJpZ2h0IDIwMTcgR29vZ2xlIExMQwovLwovLyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKLy8geW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLgovLyBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKLy8KLy8gICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKLy8KLy8gVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQovLyBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAovLyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KLy8gU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAovLyBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCi8qKgogKiBAZmlsZW92ZXJ2aWV3IEhlbHBlcnMgZm9yIGdvb2dsZS5jb2xhYiBQeXRob24gbW9kdWxlLgogKi8KKGZ1bmN0aW9uKHNjb3BlKSB7CmZ1bmN0aW9uIHNwYW4odGV4dCwgc3R5bGVBdHRyaWJ1dGVzID0ge30pIHsKICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3BhbicpOwogIGVsZW1lbnQudGV4dENvbnRlbnQgPSB0ZXh0OwogIGZvciAoY29uc3Qga2V5IG9mIE9iamVjdC5rZXlzKHN0eWxlQXR0cmlidXRlcykpIHsKICAgIGVsZW1lbnQuc3R5bGVba2V5XSA9IHN0eWxlQXR0cmlidXRlc1trZXldOwogIH0KICByZXR1cm4gZWxlbWVudDsKfQoKLy8gTWF4IG51bWJlciBvZiBieXRlcyB3aGljaCB3aWxsIGJlIHVwbG9hZGVkIGF0IGEgdGltZS4KY29uc3QgTUFYX1BBWUxPQURfU0laRSA9IDEwMCAqIDEwMjQ7CgpmdW5jdGlvbiBfdXBsb2FkRmlsZXMoaW5wdXRJZCwgb3V0cHV0SWQpIHsKICBjb25zdCBzdGVwcyA9IHVwbG9hZEZpbGVzU3RlcChpbnB1dElkLCBvdXRwdXRJZCk7CiAgY29uc3Qgb3V0cHV0RWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKG91dHB1dElkKTsKICAvLyBDYWNoZSBzdGVwcyBvbiB0aGUgb3V0cHV0RWxlbWVudCB0byBtYWtlIGl0IGF2YWlsYWJsZSBmb3IgdGhlIG5leHQgY2FsbAogIC8vIHRvIHVwbG9hZEZpbGVzQ29udGludWUgZnJvbSBQeXRob24uCiAgb3V0cHV0RWxlbWVudC5zdGVwcyA9IHN0ZXBzOwoKICByZXR1cm4gX3VwbG9hZEZpbGVzQ29udGludWUob3V0cHV0SWQpOwp9CgovLyBUaGlzIGlzIHJvdWdobHkgYW4gYXN5bmMgZ2VuZXJhdG9yIChub3Qgc3VwcG9ydGVkIGluIHRoZSBicm93c2VyIHlldCksCi8vIHdoZXJlIHRoZXJlIGFyZSBtdWx0aXBsZSBhc3luY2hyb25vdXMgc3RlcHMgYW5kIHRoZSBQeXRob24gc2lkZSBpcyBnb2luZwovLyB0byBwb2xsIGZvciBjb21wbGV0aW9uIG9mIGVhY2ggc3RlcC4KLy8gVGhpcyB1c2VzIGEgUHJvbWlzZSB0byBibG9jayB0aGUgcHl0aG9uIHNpZGUgb24gY29tcGxldGlvbiBvZiBlYWNoIHN0ZXAsCi8vIHRoZW4gcGFzc2VzIHRoZSByZXN1bHQgb2YgdGhlIHByZXZpb3VzIHN0ZXAgYXMgdGhlIGlucHV0IHRvIHRoZSBuZXh0IHN0ZXAuCmZ1bmN0aW9uIF91cGxvYWRGaWxlc0NvbnRpbnVlKG91dHB1dElkKSB7CiAgY29uc3Qgb3V0cHV0RWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKG91dHB1dElkKTsKICBjb25zdCBzdGVwcyA9IG91dHB1dEVsZW1lbnQuc3RlcHM7CgogIGNvbnN0IG5leHQgPSBzdGVwcy5uZXh0KG91dHB1dEVsZW1lbnQubGFzdFByb21pc2VWYWx1ZSk7CiAgcmV0dXJuIFByb21pc2UucmVzb2x2ZShuZXh0LnZhbHVlLnByb21pc2UpLnRoZW4oKHZhbHVlKSA9PiB7CiAgICAvLyBDYWNoZSB0aGUgbGFzdCBwcm9taXNlIHZhbHVlIHRvIG1ha2UgaXQgYXZhaWxhYmxlIHRvIHRoZSBuZXh0CiAgICAvLyBzdGVwIG9mIHRoZSBnZW5lcmF0b3IuCiAgICBvdXRwdXRFbGVtZW50Lmxhc3RQcm9taXNlVmFsdWUgPSB2YWx1ZTsKICAgIHJldHVybiBuZXh0LnZhbHVlLnJlc3BvbnNlOwogIH0pOwp9CgovKioKICogR2VuZXJhdG9yIGZ1bmN0aW9uIHdoaWNoIGlzIGNhbGxlZCBiZXR3ZWVuIGVhY2ggYXN5bmMgc3RlcCBvZiB0aGUgdXBsb2FkCiAqIHByb2Nlc3MuCiAqIEBwYXJhbSB7c3RyaW5nfSBpbnB1dElkIEVsZW1lbnQgSUQgb2YgdGhlIGlucHV0IGZpbGUgcGlja2VyIGVsZW1lbnQuCiAqIEBwYXJhbSB7c3RyaW5nfSBvdXRwdXRJZCBFbGVtZW50IElEIG9mIHRoZSBvdXRwdXQgZGlzcGxheS4KICogQHJldHVybiB7IUl0ZXJhYmxlPCFPYmplY3Q+fSBJdGVyYWJsZSBvZiBuZXh0IHN0ZXBzLgogKi8KZnVuY3Rpb24qIHVwbG9hZEZpbGVzU3RlcChpbnB1dElkLCBvdXRwdXRJZCkgewogIGNvbnN0IGlucHV0RWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlucHV0SWQpOwogIGlucHV0RWxlbWVudC5kaXNhYmxlZCA9IGZhbHNlOwoKICBjb25zdCBvdXRwdXRFbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQob3V0cHV0SWQpOwogIG91dHB1dEVsZW1lbnQuaW5uZXJIVE1MID0gJyc7CgogIGNvbnN0IHBpY2tlZFByb21pc2UgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gewogICAgaW5wdXRFbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2NoYW5nZScsIChlKSA9PiB7CiAgICAgIHJlc29sdmUoZS50YXJnZXQuZmlsZXMpOwogICAgfSk7CiAgfSk7CgogIGNvbnN0IGNhbmNlbCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2J1dHRvbicpOwogIGlucHV0RWxlbWVudC5wYXJlbnRFbGVtZW50LmFwcGVuZENoaWxkKGNhbmNlbCk7CiAgY2FuY2VsLnRleHRDb250ZW50ID0gJ0NhbmNlbCB1cGxvYWQnOwogIGNvbnN0IGNhbmNlbFByb21pc2UgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gewogICAgY2FuY2VsLm9uY2xpY2sgPSAoKSA9PiB7CiAgICAgIHJlc29sdmUobnVsbCk7CiAgICB9OwogIH0pOwoKICAvLyBXYWl0IGZvciB0aGUgdXNlciB0byBwaWNrIHRoZSBmaWxlcy4KICBjb25zdCBmaWxlcyA9IHlpZWxkIHsKICAgIHByb21pc2U6IFByb21pc2UucmFjZShbcGlja2VkUHJvbWlzZSwgY2FuY2VsUHJvbWlzZV0pLAogICAgcmVzcG9uc2U6IHsKICAgICAgYWN0aW9uOiAnc3RhcnRpbmcnLAogICAgfQogIH07CgogIGNhbmNlbC5yZW1vdmUoKTsKCiAgLy8gRGlzYWJsZSB0aGUgaW5wdXQgZWxlbWVudCBzaW5jZSBmdXJ0aGVyIHBpY2tzIGFyZSBub3QgYWxsb3dlZC4KICBpbnB1dEVsZW1lbnQuZGlzYWJsZWQgPSB0cnVlOwoKICBpZiAoIWZpbGVzKSB7CiAgICByZXR1cm4gewogICAgICByZXNwb25zZTogewogICAgICAgIGFjdGlvbjogJ2NvbXBsZXRlJywKICAgICAgfQogICAgfTsKICB9CgogIGZvciAoY29uc3QgZmlsZSBvZiBmaWxlcykgewogICAgY29uc3QgbGkgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdsaScpOwogICAgbGkuYXBwZW5kKHNwYW4oZmlsZS5uYW1lLCB7Zm9udFdlaWdodDogJ2JvbGQnfSkpOwogICAgbGkuYXBwZW5kKHNwYW4oCiAgICAgICAgYCgke2ZpbGUudHlwZSB8fCAnbi9hJ30pIC0gJHtmaWxlLnNpemV9IGJ5dGVzLCBgICsKICAgICAgICBgbGFzdCBtb2RpZmllZDogJHsKICAgICAgICAgICAgZmlsZS5sYXN0TW9kaWZpZWREYXRlID8gZmlsZS5sYXN0TW9kaWZpZWREYXRlLnRvTG9jYWxlRGF0ZVN0cmluZygpIDoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ24vYSd9IC0gYCkpOwogICAgY29uc3QgcGVyY2VudCA9IHNwYW4oJzAlIGRvbmUnKTsKICAgIGxpLmFwcGVuZENoaWxkKHBlcmNlbnQpOwoKICAgIG91dHB1dEVsZW1lbnQuYXBwZW5kQ2hpbGQobGkpOwoKICAgIGNvbnN0IGZpbGVEYXRhUHJvbWlzZSA9IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7CiAgICAgIGNvbnN0IHJlYWRlciA9IG5ldyBGaWxlUmVhZGVyKCk7CiAgICAgIHJlYWRlci5vbmxvYWQgPSAoZSkgPT4gewogICAgICAgIHJlc29sdmUoZS50YXJnZXQucmVzdWx0KTsKICAgICAgfTsKICAgICAgcmVhZGVyLnJlYWRBc0FycmF5QnVmZmVyKGZpbGUpOwogICAgfSk7CiAgICAvLyBXYWl0IGZvciB0aGUgZGF0YSB0byBiZSByZWFkeS4KICAgIGxldCBmaWxlRGF0YSA9IHlpZWxkIHsKICAgICAgcHJvbWlzZTogZmlsZURhdGFQcm9taXNlLAogICAgICByZXNwb25zZTogewogICAgICAgIGFjdGlvbjogJ2NvbnRpbnVlJywKICAgICAgfQogICAgfTsKCiAgICAvLyBVc2UgYSBjaHVua2VkIHNlbmRpbmcgdG8gYXZvaWQgbWVzc2FnZSBzaXplIGxpbWl0cy4gU2VlIGIvNjIxMTU2NjAuCiAgICBsZXQgcG9zaXRpb24gPSAwOwogICAgZG8gewogICAgICBjb25zdCBsZW5ndGggPSBNYXRoLm1pbihmaWxlRGF0YS5ieXRlTGVuZ3RoIC0gcG9zaXRpb24sIE1BWF9QQVlMT0FEX1NJWkUpOwogICAgICBjb25zdCBjaHVuayA9IG5ldyBVaW50OEFycmF5KGZpbGVEYXRhLCBwb3NpdGlvbiwgbGVuZ3RoKTsKICAgICAgcG9zaXRpb24gKz0gbGVuZ3RoOwoKICAgICAgY29uc3QgYmFzZTY0ID0gYnRvYShTdHJpbmcuZnJvbUNoYXJDb2RlLmFwcGx5KG51bGwsIGNodW5rKSk7CiAgICAgIHlpZWxkIHsKICAgICAgICByZXNwb25zZTogewogICAgICAgICAgYWN0aW9uOiAnYXBwZW5kJywKICAgICAgICAgIGZpbGU6IGZpbGUubmFtZSwKICAgICAgICAgIGRhdGE6IGJhc2U2NCwKICAgICAgICB9LAogICAgICB9OwoKICAgICAgbGV0IHBlcmNlbnREb25lID0gZmlsZURhdGEuYnl0ZUxlbmd0aCA9PT0gMCA/CiAgICAgICAgICAxMDAgOgogICAgICAgICAgTWF0aC5yb3VuZCgocG9zaXRpb24gLyBmaWxlRGF0YS5ieXRlTGVuZ3RoKSAqIDEwMCk7CiAgICAgIHBlcmNlbnQudGV4dENvbnRlbnQgPSBgJHtwZXJjZW50RG9uZX0lIGRvbmVgOwoKICAgIH0gd2hpbGUgKHBvc2l0aW9uIDwgZmlsZURhdGEuYnl0ZUxlbmd0aCk7CiAgfQoKICAvLyBBbGwgZG9uZS4KICB5aWVsZCB7CiAgICByZXNwb25zZTogewogICAgICBhY3Rpb246ICdjb21wbGV0ZScsCiAgICB9CiAgfTsKfQoKc2NvcGUuZ29vZ2xlID0gc2NvcGUuZ29vZ2xlIHx8IHt9OwpzY29wZS5nb29nbGUuY29sYWIgPSBzY29wZS5nb29nbGUuY29sYWIgfHwge307CnNjb3BlLmdvb2dsZS5jb2xhYi5fZmlsZXMgPSB7CiAgX3VwbG9hZEZpbGVzLAogIF91cGxvYWRGaWxlc0NvbnRpbnVlLAp9Owp9KShzZWxmKTsK",
"ok": true,
"headers": [
[
"content-type",
"application/javascript"
]
],
"status": 200,
"status_text": ""
}
},
"base_uri": "https://localhost:8080/",
"height": 92
},
"id": "TYy9ckT3HZUc",
"outputId": "13093e67-0013-4e9f-b3d8-eedfc11a997a"
},
"execution_count": 4,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
""
],
"text/html": [
"\n",
" \n",
" \n",
" "
]
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"Saving kaggle.json to kaggle.json\n"
]
},
{
"output_type": "execute_result",
"data": {
"text/plain": [
"{'kaggle.json': b'{\"username\":\"phonchi\",\"key\":\"543eb33eabf413fb77a6b374f96ccfca\"}'}"
]
},
"metadata": {},
"execution_count": 4
}
]
},
{
"cell_type": "code",
"source": [
"!mkdir ~/.kaggle\n",
"!cp kaggle.json ~/.kaggle/\n",
"!chmod 600 ~/.kaggle/kaggle.json"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "VNodHm_dHasj",
"outputId": "5d80d38a-a7df-4629-c5a6-bbaa0f0de41b"
},
"execution_count": 5,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"mkdir: cannot create directory ‘/root/.kaggle’: File exists\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"!kaggle datasets download -d maxhorowitz/nflplaybyplay2009to2016"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "r5e40YRFG33c",
"outputId": "a5cabf48-0e31-462a-f0a7-5515d493d464"
},
"execution_count": 10,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Downloading nflplaybyplay2009to2016.zip to /content\n",
" 94% 258M/274M [00:01<00:00, 151MB/s]\n",
"100% 274M/274M [00:01<00:00, 145MB/s]\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"!unzip -qq nflplaybyplay2009to2016"
],
"metadata": {
"id": "AzG450eiHmfD"
},
"execution_count": 11,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# read in all our data\n",
"nfl_data = pd.read_csv(\"NFL Play by Play 2009-2017 (v4).csv\")\n",
"\n",
"# set seed for reproducibility\n",
"np.random.seed(0) "
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "fbcx3ZlRFAoq",
"outputId": "95c7e597-07fb-4158-a989-8d53c811b5ff"
},
"execution_count": 12,
"outputs": [
{
"output_type": "stream",
"name": "stderr",
"text": [
"/usr/local/lib/python3.7/dist-packages/IPython/core/interactiveshell.py:2882: DtypeWarning: Columns (25,51) have mixed types.Specify dtype option on import or set low_memory=False.\n",
" exec(code_obj, self.user_global_ns, self.user_ns)\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"The first thing to do when you get a new dataset is take a look at some of it. This lets you see that it all read in correctly and gives an idea of what's going on with the data. In this case, let's see if there are any missing values, which will be reprsented with `NaN` or `None`."
],
"metadata": {
"id": "CFpSwmOJIDS_"
}
},
{
"cell_type": "code",
"source": [
"# look at the first five rows of the nfl_data file. \n",
"# I can see a handful of missing data already!\n",
"nfl_data.head()"
],
"metadata": {
"id": "BnVn82RDGyQu",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 386
},
"outputId": "3ff773ac-6c4e-4918-891d-d34ed28c8f7e"
},
"execution_count": 13,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" Date GameID Drive qtr down time TimeUnder TimeSecs \\\n",
"0 2009-09-10 2009091000 1 1 NaN 15:00 15 3600.0 \n",
"1 2009-09-10 2009091000 1 1 1.0 14:53 15 3593.0 \n",
"2 2009-09-10 2009091000 1 1 2.0 14:16 15 3556.0 \n",
"3 2009-09-10 2009091000 1 1 3.0 13:35 14 3515.0 \n",
"4 2009-09-10 2009091000 1 1 4.0 13:27 14 3507.0 \n",
"\n",
" PlayTimeDiff SideofField ... yacEPA Home_WP_pre Away_WP_pre \\\n",
"0 0.0 TEN ... NaN 0.485675 0.514325 \n",
"1 7.0 PIT ... 1.146076 0.546433 0.453567 \n",
"2 37.0 PIT ... NaN 0.551088 0.448912 \n",
"3 41.0 PIT ... -5.031425 0.510793 0.489207 \n",
"4 8.0 PIT ... NaN 0.461217 0.538783 \n",
"\n",
" Home_WP_post Away_WP_post Win_Prob WPA airWPA yacWPA Season \n",
"0 0.546433 0.453567 0.485675 0.060758 NaN NaN 2009 \n",
"1 0.551088 0.448912 0.546433 0.004655 -0.032244 0.036899 2009 \n",
"2 0.510793 0.489207 0.551088 -0.040295 NaN NaN 2009 \n",
"3 0.461217 0.538783 0.510793 -0.049576 0.106663 -0.156239 2009 \n",
"4 0.558929 0.441071 0.461217 0.097712 NaN NaN 2009 \n",
"\n",
"[5 rows x 102 columns]"
],
"text/html": [
"\n",
"
\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
Date
\n",
"
GameID
\n",
"
Drive
\n",
"
qtr
\n",
"
down
\n",
"
time
\n",
"
TimeUnder
\n",
"
TimeSecs
\n",
"
PlayTimeDiff
\n",
"
SideofField
\n",
"
...
\n",
"
yacEPA
\n",
"
Home_WP_pre
\n",
"
Away_WP_pre
\n",
"
Home_WP_post
\n",
"
Away_WP_post
\n",
"
Win_Prob
\n",
"
WPA
\n",
"
airWPA
\n",
"
yacWPA
\n",
"
Season
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
2009-09-10
\n",
"
2009091000
\n",
"
1
\n",
"
1
\n",
"
NaN
\n",
"
15:00
\n",
"
15
\n",
"
3600.0
\n",
"
0.0
\n",
"
TEN
\n",
"
...
\n",
"
NaN
\n",
"
0.485675
\n",
"
0.514325
\n",
"
0.546433
\n",
"
0.453567
\n",
"
0.485675
\n",
"
0.060758
\n",
"
NaN
\n",
"
NaN
\n",
"
2009
\n",
"
\n",
"
\n",
"
1
\n",
"
2009-09-10
\n",
"
2009091000
\n",
"
1
\n",
"
1
\n",
"
1.0
\n",
"
14:53
\n",
"
15
\n",
"
3593.0
\n",
"
7.0
\n",
"
PIT
\n",
"
...
\n",
"
1.146076
\n",
"
0.546433
\n",
"
0.453567
\n",
"
0.551088
\n",
"
0.448912
\n",
"
0.546433
\n",
"
0.004655
\n",
"
-0.032244
\n",
"
0.036899
\n",
"
2009
\n",
"
\n",
"
\n",
"
2
\n",
"
2009-09-10
\n",
"
2009091000
\n",
"
1
\n",
"
1
\n",
"
2.0
\n",
"
14:16
\n",
"
15
\n",
"
3556.0
\n",
"
37.0
\n",
"
PIT
\n",
"
...
\n",
"
NaN
\n",
"
0.551088
\n",
"
0.448912
\n",
"
0.510793
\n",
"
0.489207
\n",
"
0.551088
\n",
"
-0.040295
\n",
"
NaN
\n",
"
NaN
\n",
"
2009
\n",
"
\n",
"
\n",
"
3
\n",
"
2009-09-10
\n",
"
2009091000
\n",
"
1
\n",
"
1
\n",
"
3.0
\n",
"
13:35
\n",
"
14
\n",
"
3515.0
\n",
"
41.0
\n",
"
PIT
\n",
"
...
\n",
"
-5.031425
\n",
"
0.510793
\n",
"
0.489207
\n",
"
0.461217
\n",
"
0.538783
\n",
"
0.510793
\n",
"
-0.049576
\n",
"
0.106663
\n",
"
-0.156239
\n",
"
2009
\n",
"
\n",
"
\n",
"
4
\n",
"
2009-09-10
\n",
"
2009091000
\n",
"
1
\n",
"
1
\n",
"
4.0
\n",
"
13:27
\n",
"
14
\n",
"
3507.0
\n",
"
8.0
\n",
"
PIT
\n",
"
...
\n",
"
NaN
\n",
"
0.461217
\n",
"
0.538783
\n",
"
0.558929
\n",
"
0.441071
\n",
"
0.461217
\n",
"
0.097712
\n",
"
NaN
\n",
"
NaN
\n",
"
2009
\n",
"
\n",
" \n",
"
\n",
"
5 rows × 102 columns
\n",
"
\n",
" \n",
" \n",
" \n",
"\n",
" \n",
"
\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 13
}
]
},
{
"cell_type": "code",
"source": [
"nfl_data.shape"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "NQa9Dh5eGJS6",
"outputId": "869166ca-a454-4554-beae-211a8d8c3f79"
},
"execution_count": 14,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"(407688, 102)"
]
},
"metadata": {},
"execution_count": 14
}
]
},
{
"cell_type": "markdown",
"source": [
"### How many missing data points do we have?"
],
"metadata": {
"id": "xOQuDCL2IKqL"
}
},
{
"cell_type": "markdown",
"source": [
"Ok, now we know that we do have some missing values. Let's see how many we have in each column. "
],
"metadata": {
"id": "5cfCvRIHEQWS"
}
},
{
"cell_type": "code",
"source": [
"# get the number of missing data points per column\n",
"missing_values_count = nfl_data.isnull().sum()\n",
"\n",
"# look at the # of missing points in the first ten columns\n",
"missing_values_count[0:10]"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "_eoOztEcEPvz",
"outputId": "6d6a549e-57f0-4f49-a116-35e76e8e94dd"
},
"execution_count": 15,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Date 0\n",
"GameID 0\n",
"Drive 0\n",
"qtr 0\n",
"down 61154\n",
"time 224\n",
"TimeUnder 0\n",
"TimeSecs 224\n",
"PlayTimeDiff 444\n",
"SideofField 528\n",
"dtype: int64"
]
},
"metadata": {},
"execution_count": 15
}
]
},
{
"cell_type": "code",
"source": [
"# how many total missing values do we have?\n",
"total_cells = np.product(nfl_data.shape)\n",
"total_missing = missing_values_count.sum()\n",
"\n",
"# percent of data that is missing\n",
"percent_missing = (total_missing/total_cells) * 100\n",
"print(percent_missing)"
],
"metadata": {
"id": "vcmdamwKG3Et",
"colab": {
"base_uri": "https://localhost:8080/"
},
"outputId": "ad866b6c-d605-4b28-b540-34da1851f377"
},
"execution_count": 16,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"24.87214126835169\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"Almost a quarter of the cells in this dataset are empty! In the next step, we're going to take a closer look at some of the columns with missing values and try to figure out what might be going on with them."
],
"metadata": {
"id": "Cuhj2tk7EhBD"
}
},
{
"cell_type": "markdown",
"source": [
"Looking at the number of missing values in the `nfl_data` dataframe, we notice that the column \"TimesSec\" has a lot of missing values in it. By looking at [the documentation](https://www.kaggle.com/maxhorowitz/nflplaybyplay2009to2016), we can see that this column has information on the number of seconds left in the game when the play was made. This means that these values are probably missing because **they were not recorded**, rather than because they don't exist. So, it would make sense for us to try and guess what they should be rather than just leaving them as NA's.\n",
"\n",
"On the other hand, there are other fields, like \"PenalizedTeam\" that also have lot of missing fields. In this case, though, the field is missing because if there was no penalty then it doesn't make sense to say *which* team was penalized. For this column, it would make more sense to either leave it empty or to add a third value like \"neither\" and use that to replace the NA's.\n",
"\n",
"We'll cover some \"quick and dirty\" techniques that can help you with missing values but will probably also end up removing some useful information or adding some noise to your data."
],
"metadata": {
"id": "1UK6R1kBIaPC"
}
},
{
"cell_type": "markdown",
"source": [
"### Drop missing values"
],
"metadata": {
"id": "ssJU-WK9MSSE"
}
},
{
"cell_type": "markdown",
"source": [
"If you're sure you want to drop rows with missing values, pandas does have a handy function, `dropna()` to help you do this. Let's try it out on our NFL dataset!"
],
"metadata": {
"id": "YRCSulcqCL_o"
}
},
{
"cell_type": "code",
"source": [
"# remove all the rows that contain a missing value\n",
"# This is because every row in our dataset had at least one missing value. \n",
"# We might have better luck removing all the *columns* that have at least one missing value instead.\n",
"nfl_data.dropna()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 123
},
"id": "8Zb1pyvFOvan",
"outputId": "100426de-d265-4e23-d7d9-b6a52c56f7ed"
},
"execution_count": 17,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Empty DataFrame\n",
"Columns: [Date, GameID, Drive, qtr, down, time, TimeUnder, TimeSecs, PlayTimeDiff, SideofField, yrdln, yrdline100, ydstogo, ydsnet, GoalToGo, FirstDown, posteam, DefensiveTeam, desc, PlayAttempted, Yards.Gained, sp, Touchdown, ExPointResult, TwoPointConv, DefTwoPoint, Safety, Onsidekick, PuntResult, PlayType, Passer, Passer_ID, PassAttempt, PassOutcome, PassLength, AirYards, YardsAfterCatch, QBHit, PassLocation, InterceptionThrown, Interceptor, Rusher, Rusher_ID, RushAttempt, RunLocation, RunGap, Receiver, Receiver_ID, Reception, ReturnResult, Returner, BlockingPlayer, Tackler1, Tackler2, FieldGoalResult, FieldGoalDistance, Fumble, RecFumbTeam, RecFumbPlayer, Sack, Challenge.Replay, ChalReplayResult, Accepted.Penalty, PenalizedTeam, PenaltyType, PenalizedPlayer, Penalty.Yards, PosTeamScore, DefTeamScore, ScoreDiff, AbsScoreDiff, HomeTeam, AwayTeam, Timeout_Indicator, Timeout_Team, posteam_timeouts_pre, HomeTimeouts_Remaining_Pre, AwayTimeouts_Remaining_Pre, HomeTimeouts_Remaining_Post, AwayTimeouts_Remaining_Post, No_Score_Prob, Opp_Field_Goal_Prob, Opp_Safety_Prob, Opp_Touchdown_Prob, Field_Goal_Prob, Safety_Prob, Touchdown_Prob, ExPoint_Prob, TwoPoint_Prob, ExpPts, EPA, airEPA, yacEPA, Home_WP_pre, Away_WP_pre, Home_WP_post, Away_WP_post, Win_Prob, WPA, airWPA, ...]\n",
"Index: []\n",
"\n",
"[0 rows x 102 columns]"
],
"text/html": [
"\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 18
}
]
},
{
"cell_type": "code",
"source": [
"# just how much data did we lose?\n",
"print(\"Columns in original dataset: %d \\n\" % nfl_data.shape[1])\n",
"print(\"Columns with na's dropped: %d\" % columns_with_na_dropped.shape[1])"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "GSafb8UwPF7D",
"outputId": "c85791c3-6701-47b0-cc86-6d0819d7af6e"
},
"execution_count": 19,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Columns in original dataset: 102 \n",
"\n",
"Columns with na's dropped: 41\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"Notice that this can drop a lot of data that you might want to keep, particularly in smaller datasets. What if you just want to drop rows or columns that contain several or even just all null values? You specify those setting in dropna with the `how` and `thresh` parameters.\n",
"\n",
"By default, `how='any'`. You could alternatively specify `how='all'` so as to **drop only rows or columns that contain all null values**. The `thresh `parameter gives you finer-grained control: you set the number of non-null values that a row or column needs to have in order to be kept."
],
"metadata": {
"id": "PVH46JhrRmFM"
}
},
{
"cell_type": "code",
"source": [
"df1 = pd.DataFrame([[ 1, np.nan, 7], \n",
" [ 2, 5, 8], \n",
" [ np.nan, 6, 9]])\n",
"df1[3] = np.nan\n",
"df1"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 143
},
"id": "DNdAO30GRiUl",
"outputId": "a763e5df-5244-4225-8f81-dd51753964f1"
},
"execution_count": 36,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" 0 1 2 3\n",
"0 1.0 NaN 7 NaN\n",
"1 2.0 5.0 8 NaN\n",
"2 NaN 6.0 9 NaN"
],
"text/html": [
"\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 37
}
]
},
{
"cell_type": "markdown",
"source": [
"Here, the first and last row have been dropped, because they contain only two non-null values."
],
"metadata": {
"id": "1rmeQjdQSmst"
}
},
{
"cell_type": "markdown",
"source": [
"### Filling in missing values automatically"
],
"metadata": {
"id": "67yq6yZjPOQM"
}
},
{
"cell_type": "markdown",
"source": [
"Depending on your dataset, it can sometimes make more sense to fill null values with valid ones rather than drop them. Pandas provides `fillna`, which returns a copy of the `Series` or `DataFrame` with the missing values replaced with one of your choosing. Let's create another example `Series` to see how this works in practice."
],
"metadata": {
"id": "m_PPUSnnPUnU"
}
},
{
"cell_type": "code",
"source": [
"# You can fill all of the null entries with a single value, such as 0:\n",
"df1.fillna(0)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 143
},
"id": "wQolS3TCTAuH",
"outputId": "2904fef8-35b6-4390-f725-dc32a7a35bb1"
},
"execution_count": 38,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" 0 1 2 3\n",
"0 1.0 0.0 7 0.0\n",
"1 2.0 5.0 8 0.0\n",
"2 0.0 6.0 9 0.0"
],
"text/html": [
"\n",
"
\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
0
\n",
"
1
\n",
"
2
\n",
"
3
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
1.0
\n",
"
0.0
\n",
"
7
\n",
"
0.0
\n",
"
\n",
"
\n",
"
1
\n",
"
2.0
\n",
"
5.0
\n",
"
8
\n",
"
0.0
\n",
"
\n",
"
\n",
"
2
\n",
"
0.0
\n",
"
6.0
\n",
"
9
\n",
"
0.0
\n",
"
\n",
" \n",
"
\n",
"
\n",
" \n",
" \n",
" \n",
"\n",
" \n",
"
\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 38
}
]
},
{
"cell_type": "markdown",
"source": [
"We could also replace missing values with whatever value comes directly after/before it in the same column. (This makes a lot of sense for datasets where the observations have some sort of logical order to them.)\n",
"\n",
"You can forward-fill null values, which is to use the last valid value to fill a null:"
],
"metadata": {
"id": "1Y8LvnwUTG01"
}
},
{
"cell_type": "code",
"source": [
"df1.fillna(method='ffill', axis=0)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 143
},
"id": "Q5s_EBR-TLaN",
"outputId": "700b49a5-4c99-406f-f389-3b409922f08b"
},
"execution_count": 39,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" 0 1 2 3\n",
"0 1.0 NaN 7 NaN\n",
"1 2.0 5.0 8 NaN\n",
"2 2.0 6.0 9 NaN"
],
"text/html": [
"\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 26
}
]
},
{
"cell_type": "markdown",
"source": [
"Both `duplicated` and `drop_duplicates` default to consider all columns but you can specify that they examine only a subset of columns in your DataFrame:"
],
"metadata": {
"id": "SwPOg52Rm6kz"
}
},
{
"cell_type": "code",
"source": [
"df3.drop_duplicates(['letters'])"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 112
},
"id": "C3gTUz1nm9Fp",
"outputId": "4ad0cbe7-6eca-4674-870f-65c90fa0c4fd"
},
"execution_count": 27,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" letters numbers\n",
"0 A 1\n",
"1 B 2"
],
"text/html": [
"\n",
"
\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
letters
\n",
"
numbers
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
A
\n",
"
1
\n",
"
\n",
"
\n",
"
1
\n",
"
B
\n",
"
2
\n",
"
\n",
" \n",
"
\n",
"
\n",
" \n",
" \n",
" \n",
"\n",
" \n",
"
\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 27
}
]
},
{
"cell_type": "markdown",
"source": [
"### Inconsistent data entry"
],
"metadata": {
"id": "SZ8P7-bwlOAY"
}
},
{
"cell_type": "code",
"source": [
"!kaggle datasets download -d alexisbcook/pakistan-intellectual-capital\n",
"!unzip -qq pakistan-intellectual-capital.zip"
],
"metadata": {
"id": "J02NmTcQlGOi",
"colab": {
"base_uri": "https://localhost:8080/"
},
"outputId": "918ec2e6-9a14-40fd-9188-2fa0f7f391df"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"pakistan-intellectual-capital.zip: Skipping, found more recently modified local copy (use --force to force download)\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# read in all our data\n",
"professors = pd.read_csv(\"pakistan_intellectual_capital.csv\")"
],
"metadata": {
"id": "Ci7-kF2floaV"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"professors.head()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 372
},
"id": "iidrZMSyqwSp",
"outputId": "d30e0fd8-68cb-4a72-f93c-718994268524"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" Unnamed: 0 S# Teacher Name \\\n",
"0 2 3 Dr. Abdul Basit \n",
"1 4 5 Dr. Waheed Noor \n",
"2 5 6 Dr. Junaid Baber \n",
"3 6 7 Dr. Maheen Bakhtyar \n",
"4 24 25 Samina Azim \n",
"\n",
" University Currently Teaching Department \\\n",
"0 University of Balochistan Computer Science & IT \n",
"1 University of Balochistan Computer Science & IT \n",
"2 University of Balochistan Computer Science & IT \n",
"3 University of Balochistan Computer Science & IT \n",
"4 Sardar Bahadur Khan Women's University Computer Science \n",
"\n",
" Province University Located Designation Terminal Degree \\\n",
"0 Balochistan Assistant Professor PhD \n",
"1 Balochistan Assistant Professor PhD \n",
"2 Balochistan Assistant Professor PhD \n",
"3 Balochistan Assistant Professor PhD \n",
"4 Balochistan Lecturer BS \n",
"\n",
" Graduated from Country Year \\\n",
"0 Asian Institute of Technology Thailand NaN \n",
"1 Asian Institute of Technology Thailand NaN \n",
"2 Asian Institute of Technology Thailand NaN \n",
"3 Asian Institute of Technology Thailand NaN \n",
"4 Balochistan University of Information Technolo... Pakistan 2005.0 \n",
"\n",
" Area of Specialization/Research Interests Other Information \n",
"0 Software Engineering & DBMS NaN \n",
"1 DBMS NaN \n",
"2 Information processing, Multimedia mining NaN \n",
"3 NLP, Information Retrieval, Question Answering... NaN \n",
"4 VLSI Electronics DLD Database NaN "
],
"text/html": [
"\n",
"
\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
Unnamed: 0
\n",
"
S#
\n",
"
Teacher Name
\n",
"
University Currently Teaching
\n",
"
Department
\n",
"
Province University Located
\n",
"
Designation
\n",
"
Terminal Degree
\n",
"
Graduated from
\n",
"
Country
\n",
"
Year
\n",
"
Area of Specialization/Research Interests
\n",
"
Other Information
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
2
\n",
"
3
\n",
"
Dr. Abdul Basit
\n",
"
University of Balochistan
\n",
"
Computer Science & IT
\n",
"
Balochistan
\n",
"
Assistant Professor
\n",
"
PhD
\n",
"
Asian Institute of Technology
\n",
"
Thailand
\n",
"
NaN
\n",
"
Software Engineering & DBMS
\n",
"
NaN
\n",
"
\n",
"
\n",
"
1
\n",
"
4
\n",
"
5
\n",
"
Dr. Waheed Noor
\n",
"
University of Balochistan
\n",
"
Computer Science & IT
\n",
"
Balochistan
\n",
"
Assistant Professor
\n",
"
PhD
\n",
"
Asian Institute of Technology
\n",
"
Thailand
\n",
"
NaN
\n",
"
DBMS
\n",
"
NaN
\n",
"
\n",
"
\n",
"
2
\n",
"
5
\n",
"
6
\n",
"
Dr. Junaid Baber
\n",
"
University of Balochistan
\n",
"
Computer Science & IT
\n",
"
Balochistan
\n",
"
Assistant Professor
\n",
"
PhD
\n",
"
Asian Institute of Technology
\n",
"
Thailand
\n",
"
NaN
\n",
"
Information processing, Multimedia mining
\n",
"
NaN
\n",
"
\n",
"
\n",
"
3
\n",
"
6
\n",
"
7
\n",
"
Dr. Maheen Bakhtyar
\n",
"
University of Balochistan
\n",
"
Computer Science & IT
\n",
"
Balochistan
\n",
"
Assistant Professor
\n",
"
PhD
\n",
"
Asian Institute of Technology
\n",
"
Thailand
\n",
"
NaN
\n",
"
NLP, Information Retrieval, Question Answering...
\n",
"
NaN
\n",
"
\n",
"
\n",
"
4
\n",
"
24
\n",
"
25
\n",
"
Samina Azim
\n",
"
Sardar Bahadur Khan Women's University
\n",
"
Computer Science
\n",
"
Balochistan
\n",
"
Lecturer
\n",
"
BS
\n",
"
Balochistan University of Information Technolo...
\n",
"
Pakistan
\n",
"
2005.0
\n",
"
VLSI Electronics DLD Database
\n",
"
NaN
\n",
"
\n",
" \n",
"
\n",
"
\n",
" \n",
" \n",
" \n",
"\n",
" \n",
"
\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 34
}
]
},
{
"cell_type": "markdown",
"source": [
"Say we're interested in cleaning up the \"Country\" column to make sure there's no data entry inconsistencies in it. We could go through and check each row by hand, of course, and hand-correct inconsistencies when we find them. There's a more efficient way to do this, though!"
],
"metadata": {
"id": "7bPJiliNqyeF"
}
},
{
"cell_type": "code",
"source": [
"# get all the unique values in the 'Country' column\n",
"countries = professors['Country'].unique()\n",
"\n",
"# sort them alphabetically and then take a closer look\n",
"countries.sort()\n",
"countries"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "sMLOjtJwq6Z5",
"outputId": "7cf4394d-a1ae-4708-bd0a-77686b69b990"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"array([' Germany', ' New Zealand', ' Sweden', ' USA', 'Australia',\n",
" 'Austria', 'Canada', 'China', 'Finland', 'France', 'Greece',\n",
" 'HongKong', 'Ireland', 'Italy', 'Japan', 'Macau', 'Malaysia',\n",
" 'Mauritius', 'Netherland', 'New Zealand', 'Norway', 'Pakistan',\n",
" 'Portugal', 'Russian Federation', 'Saudi Arabia', 'Scotland',\n",
" 'Singapore', 'South Korea', 'SouthKorea', 'Spain', 'Sweden',\n",
" 'Thailand', 'Turkey', 'UK', 'USA', 'USofA', 'Urbana', 'germany'],\n",
" dtype=object)"
]
},
"metadata": {},
"execution_count": 35
}
]
},
{
"cell_type": "markdown",
"source": [
"Just looking at this, we can see some problems due to inconsistent data entry: ' Germany', and 'germany', for example, or ' New Zealand' and 'New Zealand'.\n",
"\n",
"The first thing we are going to do is make everything lower case (we can change it back at the end if we like) and remove any white spaces at the beginning and end of cells. **Inconsistencies in capitalizations and trailing white spaces are very common in text data and you can fix a good 80% of your text data entry inconsistencies by doing this.**"
],
"metadata": {
"id": "a4wJWw5Lq-cC"
}
},
{
"cell_type": "code",
"source": [
"# convert to lower case\n",
"professors['Country'] = professors['Country'].str.lower()\n",
"# remove trailing white spaces\n",
"professors['Country'] = professors['Country'].str.strip()"
],
"metadata": {
"id": "Ua6m6A6RrJDD"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"#### Use fuzzy matching to correct inconsistent data entry"
],
"metadata": {
"id": "XvrcoK9zrNH7"
}
},
{
"cell_type": "markdown",
"source": [
"Alright, let's take another look at the 'Country' column and see if there's any more data cleaning we need to do"
],
"metadata": {
"id": "kE9dClzCrT57"
}
},
{
"cell_type": "code",
"source": [
"# get all the unique values in the 'Country' column\n",
"countries = professors['Country'].unique()\n",
"\n",
"# sort them alphabetically and then take a closer look\n",
"countries.sort()\n",
"countries"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "kB4mYzRErRBp",
"outputId": "df0b6365-d4c2-407d-c821-a41c16f01fa1"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"array(['australia', 'austria', 'canada', 'china', 'finland', 'france',\n",
" 'germany', 'greece', 'hongkong', 'ireland', 'italy', 'japan',\n",
" 'macau', 'malaysia', 'mauritius', 'netherland', 'new zealand',\n",
" 'norway', 'pakistan', 'portugal', 'russian federation',\n",
" 'saudi arabia', 'scotland', 'singapore', 'south korea',\n",
" 'southkorea', 'spain', 'sweden', 'thailand', 'turkey', 'uk',\n",
" 'urbana', 'usa', 'usofa'], dtype=object)"
]
},
"metadata": {},
"execution_count": 37
}
]
},
{
"cell_type": "markdown",
"source": [
"It does look like there is another inconsistency: 'southkorea' and 'south korea' should be the same. We're going to use the fuzzywuzzy package to help identify which strings are closest to each other. This dataset is small enough that we could probably correct errors by hand, but that approach doesn't scale well. (Would you want to correct a thousand errors by hand? What about ten thousand? Automating things as early as possible is generally a good idea!)\n",
"\n",
"thefuzz returns a ratio given two strings. The closer the ratio is to 100, the smaller the edit distance between the two strings. Here, we're going to get the ten strings from our list of cities that have the closest distance to \"\"south korea\""
],
"metadata": {
"id": "r-9t2G-urXqh"
}
},
{
"cell_type": "code",
"source": [
"# get the top 10 closest matches to \"south korea\"\n",
"matches = process.extract(\"south korea\", countries, limit=10, scorer=fuzz.token_sort_ratio)\n",
"\n",
"# take a look at them\n",
"matches"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "E9zmZriTsJur",
"outputId": "82933ff9-5497-4091-e90b-e26bff3a8fa7"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"[('south korea', 100),\n",
" ('southkorea', 48),\n",
" ('saudi arabia', 43),\n",
" ('norway', 35),\n",
" ('ireland', 33),\n",
" ('portugal', 32),\n",
" ('singapore', 30),\n",
" ('netherland', 29),\n",
" ('macau', 25),\n",
" ('usofa', 25)]"
]
},
"metadata": {},
"execution_count": 38
}
]
},
{
"cell_type": "markdown",
"source": [
"We can see that two of the items in the cities are very close to \"south korea\": \"south korea\" and \"southkorea\". Let's replace all rows in our \"Country\" column that have a ratio of > 47 with \"south korea\".\n",
"\n",
"To do this, we are going to write a function."
],
"metadata": {
"id": "0c_asdKQsdtW"
}
},
{
"cell_type": "code",
"source": [
"# function to replace rows in the provided column of the provided dataframe\n",
"# that match the provided string above the provided ratio with the provided string\n",
"def replace_matches_in_column(df, column, string_to_match, min_ratio = 47):\n",
" # get a list of unique strings\n",
" strings = df[column].unique()\n",
" \n",
" # get the top 10 closest matches to our input string\n",
" matches = process.extract(string_to_match, strings, limit=10, scorer=fuzz.token_sort_ratio)\n",
"\n",
" # only get matches with a ratio > 90\n",
" close_matches = [matches[0] for matches in matches if matches[1] >= min_ratio]\n",
"\n",
" # get the rows of all the close matches in our dataframe\n",
" rows_with_matches = df[column].isin(close_matches)\n",
"\n",
" # replace all rows with close matches with the input matches \n",
" df.loc[rows_with_matches, column] = string_to_match\n",
" \n",
" # let us know the function's done\n",
" print(\"All done!\")"
],
"metadata": {
"id": "vGNmSeH2sjBr"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Now that we have a function, we can put it to the test!"
],
"metadata": {
"id": "HvuBZyhdsmca"
}
},
{
"cell_type": "code",
"source": [
"# use the function we just wrote to replace close matches to \"south korea\" with \"south korea\"\n",
"replace_matches_in_column(df=professors, column='Country', string_to_match=\"south korea\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "-4jdi9Gesos0",
"outputId": "81612c64-cffd-4a65-b4f8-18185219224f"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"All done!\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"And now let's check the unique values in our \"Country\" column again and make sure we've tidied up \"south korea\" correctly."
],
"metadata": {
"id": "3b2h2I6Wsw8y"
}
},
{
"cell_type": "code",
"source": [
"# get all the unique values in the 'Country' column\n",
"countries = professors['Country'].unique()\n",
"\n",
"# sort them alphabetically and then take a closer look\n",
"countries.sort()\n",
"countries"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "KP6Q8EbEsx7a",
"outputId": "51fa8f0d-21b1-4ab3-ddbf-e4bdb65bb3e8"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"array(['australia', 'austria', 'canada', 'china', 'finland', 'france',\n",
" 'germany', 'greece', 'hongkong', 'ireland', 'italy', 'japan',\n",
" 'macau', 'malaysia', 'mauritius', 'netherland', 'new zealand',\n",
" 'norway', 'pakistan', 'portugal', 'russian federation',\n",
" 'saudi arabia', 'scotland', 'singapore', 'south korea', 'spain',\n",
" 'sweden', 'thailand', 'turkey', 'uk', 'urbana', 'usa', 'usofa'],\n",
" dtype=object)"
]
},
"metadata": {},
"execution_count": 43
}
]
},
{
"cell_type": "markdown",
"source": [
"Now we only have \"south korea\" in our dataframe and we didn't have to change anything by hand."
],
"metadata": {
"id": "dRbcksGAs80c"
}
},
{
"cell_type": "markdown",
"source": [
"### Character encoding"
],
"metadata": {
"id": "_NrXYMW8ljQK"
}
},
{
"cell_type": "markdown",
"source": [
"It was pretty hard to deal with encodings in Python 2, but thankfully in Python 3 it's a lot simpler. There are two main data types you'll encounter when working with text in Python 3. One is is the string, which is what text is by default."
],
"metadata": {
"id": "pdzhF2R_hqTZ"
}
},
{
"cell_type": "code",
"source": [
"# start with a string\n",
"before = \"This is the euro symbol: €\"\n",
"\n",
"# check to see what datatype it is\n",
"type(before)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "O-MTwIoSw_GD",
"outputId": "fb13f0dc-ad65-4092-bd83-fcc0e1712db4"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"str"
]
},
"metadata": {},
"execution_count": 1
}
]
},
{
"cell_type": "markdown",
"source": [
"The other data is the [bytes](https://docs.python.org/3.1/library/functions.html#bytes) data type, which is a sequence of integers. You can convert a string into bytes by specifying which encoding it's in:"
],
"metadata": {
"id": "D3-nZLj-hyQQ"
}
},
{
"cell_type": "code",
"source": [
"# encode it to a different encoding, replacing characters that raise errors\n",
"after = before.encode(\"utf-8\", errors=\"replace\")\n",
"\n",
"# check the type\n",
"type(after)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "zeP9vKa_hvpH",
"outputId": "5bd838bd-c6b4-48e3-9da8-3b4609111dec"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"bytes"
]
},
"metadata": {},
"execution_count": 2
}
]
},
{
"cell_type": "markdown",
"source": [
"If you look at a bytes object, you'll see that it has a b in front of it, and then maybe some text after. **That's because bytes are printed out as if they were characters encoded in ASCII**. (ASCII is an older character encoding that doesn't really work for writing any language other than English.) Here you can see that our euro symbol has been replaced with some mojibake that looks like \"\\xe2\\x82\\xac\" when it's printed as if it were an ASCII string"
],
"metadata": {
"id": "AZUMXh0Ah6sv"
}
},
{
"cell_type": "code",
"source": [
"# take a look at what the bytes look like\n",
"after"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "nxcS_Ok-h4ND",
"outputId": "fff13df0-0728-4158-c4be-d31590a8bbcc"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"b'This is the euro symbol: \\xe2\\x82\\xac'"
]
},
"metadata": {},
"execution_count": 3
}
]
},
{
"cell_type": "markdown",
"source": [
"When we convert our bytes back to a string with the correct encoding, we can see that our text is all there correctly, which is great! :)"
],
"metadata": {
"id": "JmDVeyiqiHuI"
}
},
{
"cell_type": "code",
"source": [
"# convert it back to utf-8\n",
"print(after.decode(\"utf-8\"))"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "BnylGfCMiEgM",
"outputId": "5fcc9d14-bfd9-4e33-da8f-5f8da9e2286d"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"This is the euro symbol: €\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"However, when we try to use a different encoding to map our bytes into a string, we get an error. This is because the encoding we're trying to use doesn't know what to do with the bytes we're trying to pass it. You need to tell Python the encoding that the byte string is actually supposed to be in"
],
"metadata": {
"id": "LpBpi8RriMxY"
}
},
{
"cell_type": "code",
"source": [
"# try to decode our bytes with the ascii encoding\n",
"print(after.decode(\"ascii\"))"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "IPNb51k6iKJN",
"outputId": "9ad822b9-d279-4510-ffee-e3bdbe9ac0b1"
},
"execution_count": null,
"outputs": [
{
"output_type": "error",
"ename": "UnicodeDecodeError",
"evalue": "ignored",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mUnicodeDecodeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# try to decode our bytes with the ascii encoding\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mafter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"ascii\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mUnicodeDecodeError\u001b[0m: 'ascii' codec can't decode byte 0xe2 in position 25: ordinal not in range(128)"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"We can also run into trouble if we try to use the wrong encoding to map from a string to bytes. Like we said earlier, strings are UTF-8 by default in Python 3, so if we try to treat them like they were in another encoding we'll create problems. \n",
"\n",
"For example, if we try to convert a string to bytes for ASCII using `encode()`, we can ask for the bytes to be what they would be if the text was in ASCII. Since our text isn't in ASCII, though, there will be some characters it can't handle. We can automatically replace the characters that ASCII can't handle. If we do that, however, **any characters not in ASCII will just be replaced with the unknown character.** Then, when we convert the bytes back to a string, the character will be replaced with the unknown character. The dangerous part about this is that there's not way to tell which character it *should* have been. That means we may have just made our data unusable!"
],
"metadata": {
"id": "fgsaAfggija4"
}
},
{
"cell_type": "code",
"source": [
"# start with a string\n",
"before = \"This is the euro symbol: €\"\n",
"\n",
"# encode it to a different encoding, replacing characters that raise errors\n",
"after = before.encode(\"ascii\", errors = \"replace\")\n",
"\n",
"# convert it back to utf-8\n",
"print(after.decode(\"ascii\"))\n",
"\n",
"# We've lost the original underlying byte string! It's been \n",
"# replaced with the underlying byte string for the unknown character :("
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "2NnrsGfUiYJH",
"outputId": "7378c1df-30e0-4441-fed2-984501b8c5c7"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"This is the euro symbol: ?\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"The best time to convert non UTF-8 input into UTF-8 is when you read in files, which we'll talk about next."
],
"metadata": {
"id": "4T7_FhI1jMm-"
}
},
{
"cell_type": "markdown",
"source": [
"### Reading in files with encoding problems"
],
"metadata": {
"id": "xLlyqhjtjOYo"
}
},
{
"cell_type": "markdown",
"source": [
"Most files you'll encounter will probably be encoded with UTF-8. This is what Python expects by default, so most of the time you won't run into problems. However, sometimes you'll get an error like this:"
],
"metadata": {
"id": "vIZnUmRMjSE3"
}
},
{
"cell_type": "code",
"source": [
"!kaggle datasets download -d kemical/kickstarter-projects\n",
"!unzip -qq kickstarter-projects.zip"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "2n3tzsI5jorg",
"outputId": "8391a9be-f920-4583-c76c-91f2ab256394"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"kickstarter-projects.zip: Skipping, found more recently modified local copy (use --force to force download)\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# try to read in a file not in UTF-8\n",
"kickstarter_2016 = pd.read_csv(\"ks-projects-201612.csv\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "HY-n-wlYjN5n",
"outputId": "74489ed7-284d-484f-ab4f-c91ca01aacb8"
},
"execution_count": null,
"outputs": [
{
"output_type": "error",
"ename": "UnicodeDecodeError",
"evalue": "ignored",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mUnicodeDecodeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# try to read in a file not in UTF-8\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mkickstarter_2016\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"ks-projects-201612.csv\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m/usr/local/lib/python3.7/dist-packages/pandas/util/_decorators.py\u001b[0m in \u001b[0;36mwrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 309\u001b[0m \u001b[0mstacklevel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mstacklevel\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 310\u001b[0m )\n\u001b[0;32m--> 311\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 312\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 313\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mwrapper\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.7/dist-packages/pandas/io/parsers/readers.py\u001b[0m in \u001b[0;36mread_csv\u001b[0;34m(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, error_bad_lines, warn_bad_lines, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options)\u001b[0m\n\u001b[1;32m 584\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkwds_defaults\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 585\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 586\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_read\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilepath_or_buffer\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 587\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 588\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.7/dist-packages/pandas/io/parsers/readers.py\u001b[0m in \u001b[0;36m_read\u001b[0;34m(filepath_or_buffer, kwds)\u001b[0m\n\u001b[1;32m 480\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 481\u001b[0m \u001b[0;31m# Create the parser.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 482\u001b[0;31m \u001b[0mparser\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mTextFileReader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilepath_or_buffer\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 483\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 484\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mchunksize\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0miterator\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.7/dist-packages/pandas/io/parsers/readers.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, f, engine, **kwds)\u001b[0m\n\u001b[1;32m 809\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0moptions\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"has_index_names\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"has_index_names\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 810\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 811\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_engine\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_make_engine\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mengine\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 812\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 813\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.7/dist-packages/pandas/io/parsers/readers.py\u001b[0m in \u001b[0;36m_make_engine\u001b[0;34m(self, engine)\u001b[0m\n\u001b[1;32m 1038\u001b[0m )\n\u001b[1;32m 1039\u001b[0m \u001b[0;31m# error: Too many arguments for \"ParserBase\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1040\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mmapping\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mengine\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mf\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0moptions\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# type: ignore[call-arg]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1041\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1042\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_failover_to_python\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.7/dist-packages/pandas/io/parsers/c_parser_wrapper.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, src, **kwds)\u001b[0m\n\u001b[1;32m 67\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"dtype\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mensure_dtype_objs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkwds\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"dtype\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 69\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_reader\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparsers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mTextReader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhandles\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhandle\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 70\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 71\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhandles\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.7/dist-packages/pandas/_libs/parsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader.__cinit__\u001b[0;34m()\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.7/dist-packages/pandas/_libs/parsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader._get_header\u001b[0;34m()\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.7/dist-packages/pandas/_libs/parsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader._tokenize_rows\u001b[0;34m()\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.7/dist-packages/pandas/_libs/parsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.raise_parser_error\u001b[0;34m()\u001b[0m\n",
"\u001b[0;31mUnicodeDecodeError\u001b[0m: 'utf-8' codec can't decode byte 0x99 in position 7955: invalid start byte"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"Notice that we get the same `UnicodeDecodeError` we got when we tried to decode UTF-8 bytes as if they were ASCII! **This tells us that this file isn't actually UTF-8**. We don't know what encoding it actually *is* though. One way to figure it out is to try and test a bunch of different character encodings and see if any of them work. A better way, though, is to use the chardet module to try and automatically guess what the right encoding is. It's not 100% guaranteed to be right, but it's usually faster than just trying to guess.\n",
"\n",
"We are going to just look at the first ten thousand bytes of this file. This is usually enough for a good guess about what the encoding is and is much faster than trying to look at the whole file. (Especially with a large file this can be very slow.)"
],
"metadata": {
"id": "SrChdNiOj_yK"
}
},
{
"cell_type": "code",
"source": [
"# look at the first ten thousand bytes to guess the character encoding\n",
"with open(\"ks-projects-201612.csv\", 'rb') as rawdata:\n",
" result = chardet.detect(rawdata.read(10000))\n",
"\n",
"# check what the character encoding might be\n",
"print(result)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "iYU6coz4i75j",
"outputId": "952b8393-a846-44aa-fb2b-498c70d04869"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"{'encoding': 'Windows-1252', 'confidence': 0.73, 'language': ''}\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"So chardet is 73% confidence that the right encoding is \"Windows-1252\". Let's see if that's correct:"
],
"metadata": {
"id": "igRez5z1kwxf"
}
},
{
"cell_type": "code",
"source": [
"# read in the file with the encoding detected by chardet\n",
"kickstarter_2016 = pd.read_csv(\"ks-projects-201612.csv\", encoding='Windows-1252')\n",
"\n",
"# look at the first few lines\n",
"kickstarter_2016.head()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "qS7o_H1oktRf",
"outputId": "8426fcc3-2ea5-4ffa-c7ec-23e89db3d7af"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stderr",
"text": [
"/usr/local/lib/python3.7/dist-packages/IPython/core/interactiveshell.py:2882: DtypeWarning: Columns (13,14,15) have mixed types.Specify dtype option on import or set low_memory=False.\n",
" exec(code_obj, self.user_global_ns, self.user_ns)\n"
]
},
{
"output_type": "execute_result",
"data": {
"text/plain": [
" ID name \\\n",
"0 1000002330 The Songs of Adelaide & Abullah \n",
"1 1000004038 Where is Hank? \n",
"2 1000007540 ToshiCapital Rekordz Needs Help to Complete Album \n",
"3 1000011046 Community Film Project: The Art of Neighborhoo... \n",
"4 1000014025 Monarch Espresso Bar \n",
"\n",
" category main_category currency deadline goal \\\n",
"0 Poetry Publishing GBP 2015-10-09 11:36:00 1000 \n",
"1 Narrative Film Film & Video USD 2013-02-26 00:20:50 45000 \n",
"2 Music Music USD 2012-04-16 04:24:11 5000 \n",
"3 Film & Video Film & Video USD 2015-08-29 01:00:00 19500 \n",
"4 Restaurants Food USD 2016-04-01 13:38:27 50000 \n",
"\n",
" launched pledged state backers country usd pledged \\\n",
"0 2015-08-11 12:12:28 0 failed 0 GB 0 \n",
"1 2013-01-12 00:20:50 220 failed 3 US 220 \n",
"2 2012-03-17 03:24:11 1 failed 1 US 1 \n",
"3 2015-07-04 08:35:03 1283 canceled 14 US 1283 \n",
"4 2016-02-26 13:38:27 52375 successful 224 US 52375 \n",
"\n",
" Unnamed: 13 Unnamed: 14 Unnamed: 15 Unnamed: 16 \n",
"0 NaN NaN NaN NaN \n",
"1 NaN NaN NaN NaN \n",
"2 NaN NaN NaN NaN \n",
"3 NaN NaN NaN NaN \n",
"4 NaN NaN NaN NaN "
],
"text/html": [
"\n",
"
\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
ID
\n",
"
name
\n",
"
category
\n",
"
main_category
\n",
"
currency
\n",
"
deadline
\n",
"
goal
\n",
"
launched
\n",
"
pledged
\n",
"
state
\n",
"
backers
\n",
"
country
\n",
"
usd pledged
\n",
"
Unnamed: 13
\n",
"
Unnamed: 14
\n",
"
Unnamed: 15
\n",
"
Unnamed: 16
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
1000002330
\n",
"
The Songs of Adelaide & Abullah
\n",
"
Poetry
\n",
"
Publishing
\n",
"
GBP
\n",
"
2015-10-09 11:36:00
\n",
"
1000
\n",
"
2015-08-11 12:12:28
\n",
"
0
\n",
"
failed
\n",
"
0
\n",
"
GB
\n",
"
0
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
\n",
"
\n",
"
1
\n",
"
1000004038
\n",
"
Where is Hank?
\n",
"
Narrative Film
\n",
"
Film & Video
\n",
"
USD
\n",
"
2013-02-26 00:20:50
\n",
"
45000
\n",
"
2013-01-12 00:20:50
\n",
"
220
\n",
"
failed
\n",
"
3
\n",
"
US
\n",
"
220
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
\n",
"
\n",
"
2
\n",
"
1000007540
\n",
"
ToshiCapital Rekordz Needs Help to Complete Album
\n",
"
Music
\n",
"
Music
\n",
"
USD
\n",
"
2012-04-16 04:24:11
\n",
"
5000
\n",
"
2012-03-17 03:24:11
\n",
"
1
\n",
"
failed
\n",
"
1
\n",
"
US
\n",
"
1
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
\n",
"
\n",
"
3
\n",
"
1000011046
\n",
"
Community Film Project: The Art of Neighborhoo...
\n",
"
Film & Video
\n",
"
Film & Video
\n",
"
USD
\n",
"
2015-08-29 01:00:00
\n",
"
19500
\n",
"
2015-07-04 08:35:03
\n",
"
1283
\n",
"
canceled
\n",
"
14
\n",
"
US
\n",
"
1283
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
\n",
"
\n",
"
4
\n",
"
1000014025
\n",
"
Monarch Espresso Bar
\n",
"
Restaurants
\n",
"
Food
\n",
"
USD
\n",
"
2016-04-01 13:38:27
\n",
"
50000
\n",
"
2016-02-26 13:38:27
\n",
"
52375
\n",
"
successful
\n",
"
224
\n",
"
US
\n",
"
52375
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
\n",
" \n",
"
\n",
"
\n",
" \n",
" \n",
" \n",
"\n",
" \n",
"
\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 21
}
]
},
{
"cell_type": "markdown",
"source": [
"Yep, looks like chardet was right! The file reads in with no problem (although we do get a warning about datatypes) and when we look at the first few rows it seems to be fine. \n",
"\n",
"What if the encoding chardet guesses isn't right? Since chardet is basically just a fancy guesser, sometimes it will guess the wrong encoding. **One thing you can try is looking at more or less of the file and seeing if you get a different result and then try that.**"
],
"metadata": {
"id": "CKn77TjEk4Zi"
}
},
{
"cell_type": "markdown",
"source": [
"### Saving your files with UTF-8 encoding\n"
],
"metadata": {
"id": "s15n6JwKlAzh"
}
},
{
"cell_type": "markdown",
"source": [
"Finally, once you've gone through all the trouble of getting your file into UTF-8, you'll probably want to keep it that way. The easiest way to do that is to save your files with UTF-8 encoding. The good news is, since UTF-8 is the standard encoding in Python, when you save a file it will be saved as UTF-8 by default:"
],
"metadata": {
"id": "oKHFqRrBlDZh"
}
},
{
"cell_type": "code",
"source": [
"# save our file (will be saved as UTF-8 by default!)\n",
"kickstarter_2016.to_csv(\"ks-projects-201612-utf8.csv\")"
],
"metadata": {
"id": "Qx8jLsFvkyx-"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Scaling and normalization"
],
"metadata": {
"id": "qG3ESWK0iEYp"
}
},
{
"cell_type": "markdown",
"source": [
"### Standardization"
],
"metadata": {
"id": "_BEuFwFmiVOI"
}
},
{
"cell_type": "markdown",
"source": [
"By scaling your variables, you can help compare different variables on equal footing. The preprocessing module provides the `StandardScaler` utility class, which is a quick and easy way to perform the following operation on an array-like dataset."
],
"metadata": {
"id": "D62vRfShirZH"
}
},
{
"cell_type": "code",
"source": [
"train = pd.read_csv('train_preprocessed.csv')\n",
"train_x = train.drop(['target'], axis=1)\n",
"train_y = train['target']\n",
"test_x = pd.read_csv('test_preprocessed.csv')\n",
"train_x_saved = train_x.copy()\n",
"test_x_saved = test_x.copy()\n",
"\n",
"def load_data():\n",
" train_x, test_x = train_x_saved.copy(), test_x_saved.copy()\n",
" return train_x, test_x\n",
"\n",
"train"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 488
},
"id": "oCOuMkLuiUsd",
"outputId": "9333eb7d-0271-44dd-904d-9f64336d5825"
},
"execution_count": 67,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" age sex height weight product amount medical_info_a1 \\\n",
"0 50 1 166.445608 65.016732 9 7000000 134 \n",
"1 68 0 164.334615 56.544217 0 7000000 438 \n",
"2 77 1 167.462917 54.242267 2 6000000 313 \n",
"3 17 1 177.097725 71.147762 3 8000000 342 \n",
"4 62 0 158.165788 65.240697 1 9000000 327 \n",
"... ... ... ... ... ... ... ... \n",
"9995 61 1 182.729800 73.393777 1 2000000 189 \n",
"9996 33 0 167.701136 75.006529 8 9000 426 \n",
"9997 44 0 145.609998 47.739397 8 1000 370 \n",
"9998 34 0 165.796017 57.567695 6 5000 291 \n",
"9999 31 1 180.301762 71.425135 4 1000000 288 \n",
"\n",
" medical_info_a2 medical_info_a3 medical_info_b1 ... \\\n",
"0 202 1 11 ... \n",
"1 263 3 14 ... \n",
"2 325 1 18 ... \n",
"3 213 2 11 ... \n",
"4 102 0 14 ... \n",
"... ... ... ... ... \n",
"9995 232 7 17 ... \n",
"9996 202 3 19 ... \n",
"9997 274 1 11 ... \n",
"9998 105 1 13 ... \n",
"9999 454 4 13 ... \n",
"\n",
" medical_keyword_6 medical_keyword_7 medical_keyword_8 \\\n",
"0 1 0 1 \n",
"1 0 1 1 \n",
"2 1 0 1 \n",
"3 0 0 1 \n",
"4 0 1 1 \n",
"... ... ... ... \n",
"9995 0 0 1 \n",
"9996 0 0 1 \n",
"9997 0 0 1 \n",
"9998 1 1 1 \n",
"9999 1 0 1 \n",
"\n",
" medical_keyword_9 medical_keyword_10 year month day yearmonth \\\n",
"0 0 0 2015 2 3 24182 \n",
"1 0 0 2015 5 9 24185 \n",
"2 0 0 2016 2 13 24194 \n",
"3 0 0 2015 7 6 24187 \n",
"4 1 0 2016 9 17 24201 \n",
"... ... ... ... ... ... ... \n",
"9995 1 0 2015 10 21 24190 \n",
"9996 1 0 2015 5 28 24185 \n",
"9997 0 1 2016 2 29 24194 \n",
"9998 1 0 2016 2 27 24194 \n",
"9999 0 0 2015 7 1 24187 \n",
"\n",
" target \n",
"0 0 \n",
"1 0 \n",
"2 1 \n",
"3 0 \n",
"4 1 \n",
"... ... \n",
"9995 0 \n",
"9996 0 \n",
"9997 0 \n",
"9998 0 \n",
"9999 0 \n",
"\n",
"[10000 rows x 29 columns]"
],
"text/html": [
"\n",
"
"
],
"image/png": "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\n"
},
"metadata": {
"needs_background": "light"
}
}
]
},
{
"cell_type": "markdown",
"source": [
"## Feature Engineering"
],
"metadata": {
"id": "KZD9977Qu0v8"
}
},
{
"cell_type": "markdown",
"source": [
"We'll see how adding a few synthetic features to a dataset can improve the predictive performance of a random forest model.\n",
"\n",
"The [*Concrete*](https://www.kaggle.com/sinamhd9/concrete-comprehensive-strength) dataset contains a variety of concrete formulations and the resulting product's *compressive strength*, which is a measure of how much load that kind of concrete can bear. The task for this dataset is to predict a concrete's compressive strength given its formulation."
],
"metadata": {
"id": "rswzfmJMu6gr"
}
},
{
"cell_type": "code",
"source": [
"!kaggle datasets download -d sinamhd9/concrete-comprehensive-strength\n",
"!unzip -qq concrete-comprehensive-strength.zip"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "HNKgsVNsu5-H",
"outputId": "21d4f323-f95e-47f5-81e9-a3f7a7dfa223"
},
"execution_count": 7,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Downloading concrete-comprehensive-strength.zip to /content\n",
"\r 0% 0.00/32.9k [00:00, ?B/s]\n",
"\r100% 32.9k/32.9k [00:00<00:00, 14.5MB/s]\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"df = pd.read_excel(\"Concrete_Data.xls\")\n",
"df.head()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 285
},
"id": "T-SLhsY5vXtq",
"outputId": "c0e14815-f684-4e41-bfb0-7be1210b6d40"
},
"execution_count": 2,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" Cement (component 1)(kg in a m^3 mixture) \\\n",
"0 540.0 \n",
"1 540.0 \n",
"2 332.5 \n",
"3 332.5 \n",
"4 198.6 \n",
"\n",
" Blast Furnace Slag (component 2)(kg in a m^3 mixture) \\\n",
"0 0.0 \n",
"1 0.0 \n",
"2 142.5 \n",
"3 142.5 \n",
"4 132.4 \n",
"\n",
" Fly Ash (component 3)(kg in a m^3 mixture) \\\n",
"0 0.0 \n",
"1 0.0 \n",
"2 0.0 \n",
"3 0.0 \n",
"4 0.0 \n",
"\n",
" Water (component 4)(kg in a m^3 mixture) \\\n",
"0 162.0 \n",
"1 162.0 \n",
"2 228.0 \n",
"3 228.0 \n",
"4 192.0 \n",
"\n",
" Superplasticizer (component 5)(kg in a m^3 mixture) \\\n",
"0 2.5 \n",
"1 2.5 \n",
"2 0.0 \n",
"3 0.0 \n",
"4 0.0 \n",
"\n",
" Coarse Aggregate (component 6)(kg in a m^3 mixture) \\\n",
"0 1040.0 \n",
"1 1055.0 \n",
"2 932.0 \n",
"3 932.0 \n",
"4 978.4 \n",
"\n",
" Fine Aggregate (component 7)(kg in a m^3 mixture) Age (day) \\\n",
"0 676.0 28 \n",
"1 676.0 28 \n",
"2 594.0 270 \n",
"3 594.0 365 \n",
"4 825.5 360 \n",
"\n",
" Concrete compressive strength(MPa, megapascals) \n",
"0 79.986111 \n",
"1 61.887366 \n",
"2 40.269535 \n",
"3 41.052780 \n",
"4 44.296075 "
],
"text/html": [
"\n",
"
\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
Cement (component 1)(kg in a m^3 mixture)
\n",
"
Blast Furnace Slag (component 2)(kg in a m^3 mixture)
\n",
"
Fly Ash (component 3)(kg in a m^3 mixture)
\n",
"
Water (component 4)(kg in a m^3 mixture)
\n",
"
Superplasticizer (component 5)(kg in a m^3 mixture)
\n",
"
Coarse Aggregate (component 6)(kg in a m^3 mixture)
\n",
"
Fine Aggregate (component 7)(kg in a m^3 mixture)
\n",
"
Age (day)
\n",
"
Concrete compressive strength(MPa, megapascals)
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
540.0
\n",
"
0.0
\n",
"
0.0
\n",
"
162.0
\n",
"
2.5
\n",
"
1040.0
\n",
"
676.0
\n",
"
28
\n",
"
79.986111
\n",
"
\n",
"
\n",
"
1
\n",
"
540.0
\n",
"
0.0
\n",
"
0.0
\n",
"
162.0
\n",
"
2.5
\n",
"
1055.0
\n",
"
676.0
\n",
"
28
\n",
"
61.887366
\n",
"
\n",
"
\n",
"
2
\n",
"
332.5
\n",
"
142.5
\n",
"
0.0
\n",
"
228.0
\n",
"
0.0
\n",
"
932.0
\n",
"
594.0
\n",
"
270
\n",
"
40.269535
\n",
"
\n",
"
\n",
"
3
\n",
"
332.5
\n",
"
142.5
\n",
"
0.0
\n",
"
228.0
\n",
"
0.0
\n",
"
932.0
\n",
"
594.0
\n",
"
365
\n",
"
41.052780
\n",
"
\n",
"
\n",
"
4
\n",
"
198.6
\n",
"
132.4
\n",
"
0.0
\n",
"
192.0
\n",
"
0.0
\n",
"
978.4
\n",
"
825.5
\n",
"
360
\n",
"
44.296075
\n",
"
\n",
" \n",
"
\n",
"
\n",
" \n",
" \n",
" \n",
"\n",
" \n",
"
\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 2
}
]
},
{
"cell_type": "markdown",
"source": [
"You can see here the various ingredients going into each variety of concrete. We'll see in a moment how adding some additional synthetic features derived from these can help a model to learn important relationships among them.\n",
"\n",
"We'll first establish a baseline by training the model on the un-augmented dataset. This will help us determine whether our new features are actually useful.\n",
"\n",
"Establishing baselines like this is good practice at the start of the feature engineering process. A baseline score can help you decide whether your new features are worth keeping, or whether you should discard them and possibly try something else."
],
"metadata": {
"id": "U_NSBLCPv8fb"
}
},
{
"cell_type": "code",
"source": [
"df.columns"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "3V4WKk5WwKzE",
"outputId": "1de25150-6b70-4c58-fe1a-61abb418d0f6"
},
"execution_count": 8,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Index(['Cement (component 1)(kg in a m^3 mixture)',\n",
" 'Blast Furnace Slag (component 2)(kg in a m^3 mixture)',\n",
" 'Fly Ash (component 3)(kg in a m^3 mixture)',\n",
" 'Water (component 4)(kg in a m^3 mixture)',\n",
" 'Superplasticizer (component 5)(kg in a m^3 mixture)',\n",
" 'Coarse Aggregate (component 6)(kg in a m^3 mixture)',\n",
" 'Fine Aggregate (component 7)(kg in a m^3 mixture)', 'Age (day)',\n",
" 'Concrete compressive strength(MPa, megapascals) '],\n",
" dtype='object')"
]
},
"metadata": {},
"execution_count": 8
}
]
},
{
"cell_type": "code",
"source": [
"X = df.copy()\n",
"y = X.pop(df.columns[-1])\n",
"\n",
"# Train and score baseline model\n",
"baseline = RandomForestRegressor(criterion=\"absolute_error\", random_state=0)\n",
"baseline_score = cross_val_score(\n",
" baseline, X, y, cv=5, scoring=\"neg_mean_absolute_error\"\n",
")\n",
"baseline_score = -1 * baseline_score.mean()\n",
"\n",
"print(f\"MAE Baseline Score: {baseline_score:.4}\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Eai4kjzTvbyK",
"outputId": "49388086-124a-4856-8b0b-d26140471733"
},
"execution_count": 10,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"MAE Baseline Score: 8.397\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"If you ever cook at home, you might know that the *ratio* of ingredients in a recipe is usually a better predictor of how the recipe turns out than their absolute amounts. We might reason then that ratios of the features above would be a good predictor of `CompressiveStrength`. The cell below adds three new ratio features to the dataset."
],
"metadata": {
"id": "HktfrfbRwtbd"
}
},
{
"cell_type": "code",
"source": [
"X = df.copy()\n",
"y = X.pop(df.columns[-1])\n",
"\n",
"# Create synthetic features\n",
"X[\"FCRatio\"] = X[df.columns[-2]] / X[df.columns[-3]]\n",
"X[\"AggCmtRatio\"] = (X[df.columns[-3]] + X[df.columns[-2]]) / X[df.columns[0]]\n",
"X[\"WtrCmtRatio\"] = X[df.columns[3]] / X[df.columns[0]]\n",
"\n",
"# Train and score model on dataset with additional ratio features\n",
"model = RandomForestRegressor(criterion=\"absolute_error\", random_state=0)\n",
"score = cross_val_score(\n",
" model, X, y, cv=5, scoring=\"neg_mean_absolute_error\"\n",
")\n",
"score = -1 * score.mean()\n",
"\n",
"print(f\"MAE Score with Ratio Features: {score:.4}\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "VipL8SUpv-te",
"outputId": "f9731138-bb2e-4f12-c898-7c328c509aa7"
},
"execution_count": 9,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"MAE Score with Ratio Features: 7.732\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"And sure enough, performance improved! This is evidence that these new ratio features exposed important information to the model that it wasn't detecting before."
],
"metadata": {
"id": "O804b4pcwzT-"
}
},
{
"cell_type": "markdown",
"source": [
"### Mathematical Transforms"
],
"metadata": {
"id": "aeRCTnD7SVkz"
}
},
{
"cell_type": "markdown",
"source": [
"We'll use four datasets that having a range of feature types: [*US Traffic Accidents*](https://www.kaggle.com/sobhanmoosavi/us-accidents), [*1985 Automobiles*](https://www.kaggle.com/toramky/automobile-dataset), [*Concrete Formulations*](https://www.kaggle.com/sinamhd9/concrete-comprehensive-strength), and [*Customer Lifetime Value*](https://www.kaggle.com/pankajjsh06/ibm-watson-marketing-customer-value-data). The following hidden cell loads them up."
],
"metadata": {
"id": "ije82vOGSYeD"
}
},
{
"cell_type": "code",
"source": [
"!kaggle datasets download -d sobhanmoosavi/us-accidents\n",
"!kaggle datasets download -d toramky/automobile-dataset\n",
"!kaggle datasets download -d pankajjsh06/ibm-watson-marketing-customer-value-data"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "TZI8rQ6STVPz",
"outputId": "933e3a57-a1b5-47f3-a635-81519d9a7d86"
},
"execution_count": 6,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Downloading us-accidents.zip to /content\n",
" 96% 257M/269M [00:10<00:00, 23.8MB/s]\n",
"100% 269M/269M [00:10<00:00, 27.4MB/s]\n",
"Downloading automobile-dataset.zip to /content\n",
" 0% 0.00/4.87k [00:00, ?B/s]\n",
"100% 4.87k/4.87k [00:00<00:00, 2.38MB/s]\n",
"Downloading ibm-watson-marketing-customer-value-data.zip to /content\n",
" 0% 0.00/345k [00:00, ?B/s]\n",
"100% 345k/345k [00:00<00:00, 67.1MB/s]\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"!unzip -qq us-accidents.zip\n",
"!unzip -qq automobile-dataset.zip\n",
"!unzip -qq ibm-watson-marketing-customer-value-data.zip"
],
"metadata": {
"id": "7u33Hc2QTwTk"
},
"execution_count": 8,
"outputs": []
},
{
"cell_type": "code",
"source": [
"accidents = pd.read_csv(\"US_Accidents_Dec21_updated.csv\")\n",
"autos = pd.read_csv(\"Automobile_data.csv\")\n",
"concrete = pd.read_excel(\"Concrete_Data.xls\")\n",
"customer = pd.read_csv(\"WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv\")"
],
"metadata": {
"id": "mylsSoG-wp1j"
},
"execution_count": 9,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Relationships among numerical features are often expressed through mathematical formulas, which you'll frequently come across as part of your domain research. In Pandas, you can apply arithmetic operations to columns just as if they were ordinary numbers.\n",
"\n",
"In the *Automobile* dataset are features describing a car's engine. Research yields a variety of formulas for creating potentially useful new features. The \"stroke ratio\", for instance, is a measure of how efficient an engine is versus how performant:"
],
"metadata": {
"id": "jHrHH1C_UQ8E"
}
},
{
"cell_type": "code",
"source": [
"autos.replace(\"?\", np.nan, inplace = True)\n",
"avg_bore=autos['bore'].astype('float').mean(axis=0)\n",
"autos[\"bore\"].replace(np.nan, avg_bore, inplace=True)\n",
"avg_stroke = autos[\"stroke\"].astype(\"float\").mean(axis=0)\n",
"autos[\"stroke\"].replace(np.nan, avg_stroke, inplace=True)"
],
"metadata": {
"id": "gW5Nr7OgUfWc"
},
"execution_count": 30,
"outputs": []
},
{
"cell_type": "code",
"source": [
"autos[[\"bore\", \"stroke\"]] = autos[[\"bore\", \"stroke\"]].astype(\"float\")\n",
"autos[\"stroke_ratio\"] = autos.stroke/ autos.bore\n",
"autos[[\"stroke\", \"bore\", \"stroke_ratio\"]].head()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 206
},
"id": "w4Ra4DhfUN2d",
"outputId": "63b2f52b-ebbf-43bc-a3da-54a0b835a2cd"
},
"execution_count": 39,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" stroke bore stroke_ratio\n",
"0 2.68 3.47 0.772334\n",
"1 2.68 3.47 0.772334\n",
"2 3.47 2.68 1.294776\n",
"3 3.40 3.19 1.065831\n",
"4 3.40 3.19 1.065831"
],
"text/html": [
"\n",
"
\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
stroke
\n",
"
bore
\n",
"
stroke_ratio
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
2.68
\n",
"
3.47
\n",
"
0.772334
\n",
"
\n",
"
\n",
"
1
\n",
"
2.68
\n",
"
3.47
\n",
"
0.772334
\n",
"
\n",
"
\n",
"
2
\n",
"
3.47
\n",
"
2.68
\n",
"
1.294776
\n",
"
\n",
"
\n",
"
3
\n",
"
3.40
\n",
"
3.19
\n",
"
1.065831
\n",
"
\n",
"
\n",
"
4
\n",
"
3.40
\n",
"
3.19
\n",
"
1.065831
\n",
"
\n",
" \n",
"
\n",
"
\n",
" \n",
" \n",
" \n",
"\n",
" \n",
"
\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 39
}
]
},
{
"cell_type": "markdown",
"source": [
"Data visualization can suggest transformations, often a \"reshaping\" of a feature through powers or logarithms. The distribution of `WindSpeed` in *US Accidents* is highly skewed, for instance. In this case the logarithm is effective at normalizing it:"
],
"metadata": {
"id": "vHvkZ6ksYS1G"
}
},
{
"cell_type": "code",
"source": [
"accidents.columns"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "bLiCU-mzYYuF",
"outputId": "6bd19795-f970-432f-9d12-79ac60350f8e"
},
"execution_count": 43,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Index(['ID', 'Severity', 'Start_Time', 'End_Time', 'Start_Lat', 'Start_Lng',\n",
" 'End_Lat', 'End_Lng', 'Distance(mi)', 'Description', 'Number', 'Street',\n",
" 'Side', 'City', 'County', 'State', 'Zipcode', 'Country', 'Timezone',\n",
" 'Airport_Code', 'Weather_Timestamp', 'Temperature(F)', 'Wind_Chill(F)',\n",
" 'Humidity(%)', 'Pressure(in)', 'Visibility(mi)', 'Wind_Direction',\n",
" 'Wind_Speed(mph)', 'Precipitation(in)', 'Weather_Condition', 'Amenity',\n",
" 'Bump', 'Crossing', 'Give_Way', 'Junction', 'No_Exit', 'Railway',\n",
" 'Roundabout', 'Station', 'Stop', 'Traffic_Calming', 'Traffic_Signal',\n",
" 'Turning_Loop', 'Sunrise_Sunset', 'Civil_Twilight', 'Nautical_Twilight',\n",
" 'Astronomical_Twilight'],\n",
" dtype='object')"
]
},
"metadata": {},
"execution_count": 43
}
]
},
{
"cell_type": "code",
"source": [
"# If the feature has 0.0 values, use np.log1p (log(1+x)) instead of np.log\n",
"accidents[\"LogWindSpeed\"] = accidents[\"Wind_Speed(mph)\"].apply(np.log1p)\n",
"\n",
"# Plot a comparison\n",
"fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
"sns.kdeplot(accidents[\"Wind_Speed(mph)\"], shade=True, ax=axs[0])\n",
"sns.kdeplot(accidents.LogWindSpeed, shade=True, ax=axs[1]);"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 284
},
"id": "XSYMoCNtYTy1",
"outputId": "ec76e8a7-6aef-4798-f972-55bca8f39c38"
},
"execution_count": 45,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"
\n",
" "
]
},
"metadata": {},
"execution_count": 54
}
]
},
{
"cell_type": "markdown",
"source": [
"### Group Transforms"
],
"metadata": {
"id": "zz7LnKgUZgKG"
}
},
{
"cell_type": "markdown",
"source": [
"Finally we have **Group transforms**, which aggregate information across multiple rows grouped by some category. With a group transform you can create features like: \"the average income of a person's state of residence,\" or \"the proportion of movies released on a weekday, by genre.\" If you had discovered a category interaction, a group transform over that categry could be something good to investigate.\n",
"\n",
"Using an aggregation function, a group transform combines two features: a categorical feature that provides the grouping and another feature whose values you wish to aggregate. For an \"average income by state\", you would choose `State` for the grouping feature, `mean` for the aggregation function, and `Income` for the aggregated feature. To compute this in Pandas, we use the `groupby` and `transform` methods:"
],
"metadata": {
"id": "Sxt28fbgZkCw"
}
},
{
"cell_type": "code",
"source": [
"customer[\"AverageIncome\"] = (\n",
" customer.groupby(\"State\") # for each state\n",
" [\"Income\"] # select the income\n",
" .transform(\"mean\") # and compute its mean\n",
")\n",
"\n",
"customer[[\"State\", \"Income\", \"AverageIncome\"]].head(10)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 363
},
"id": "Fe7UPXGxZrgu",
"outputId": "b635e0b1-6834-4bec-970e-e5de6d0789ce"
},
"execution_count": 49,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" State Income AverageIncome\n",
"0 Washington 56274 38122.733083\n",
"1 Arizona 0 37405.402231\n",
"2 Nevada 48767 38369.605442\n",
"3 California 0 37558.946667\n",
"4 Washington 43836 38122.733083\n",
"5 Oregon 62902 37557.283353\n",
"6 Oregon 55350 37557.283353\n",
"7 Arizona 0 37405.402231\n",
"8 Oregon 14072 37557.283353\n",
"9 Oregon 28812 37557.283353"
],
"text/html": [
"\n",
"
\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
State
\n",
"
Income
\n",
"
AverageIncome
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
Washington
\n",
"
56274
\n",
"
38122.733083
\n",
"
\n",
"
\n",
"
1
\n",
"
Arizona
\n",
"
0
\n",
"
37405.402231
\n",
"
\n",
"
\n",
"
2
\n",
"
Nevada
\n",
"
48767
\n",
"
38369.605442
\n",
"
\n",
"
\n",
"
3
\n",
"
California
\n",
"
0
\n",
"
37558.946667
\n",
"
\n",
"
\n",
"
4
\n",
"
Washington
\n",
"
43836
\n",
"
38122.733083
\n",
"
\n",
"
\n",
"
5
\n",
"
Oregon
\n",
"
62902
\n",
"
37557.283353
\n",
"
\n",
"
\n",
"
6
\n",
"
Oregon
\n",
"
55350
\n",
"
37557.283353
\n",
"
\n",
"
\n",
"
7
\n",
"
Arizona
\n",
"
0
\n",
"
37405.402231
\n",
"
\n",
"
\n",
"
8
\n",
"
Oregon
\n",
"
14072
\n",
"
37557.283353
\n",
"
\n",
"
\n",
"
9
\n",
"
Oregon
\n",
"
28812
\n",
"
37557.283353
\n",
"
\n",
" \n",
"
\n",
"
\n",
" \n",
" \n",
" \n",
"\n",
" \n",
"
\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 49
}
]
},
{
"cell_type": "markdown",
"source": [
"The `mean` function is a built-in dataframe method, which means we can pass it as a string to `transform`. Other handy methods include `max`, `min`, `median`, `var`, `std`, and `count`. Here's how you could calculate the frequency with which each state occurs in the dataset:\n"
],
"metadata": {
"id": "Y9ccRekZZ5jX"
}
},
{
"cell_type": "code",
"source": [
"customer[\"StateFreq\"] = (\n",
" customer.groupby(\"State\")\n",
" [\"State\"]\n",
" .transform(\"count\")\n",
" / customer.State.count()\n",
")\n",
"\n",
"customer[[\"State\", \"StateFreq\"]].head(10)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 363
},
"id": "ytgp7h_QZwQd",
"outputId": "3197eb08-f8e0-4ffd-dea3-5b366f706782"
},
"execution_count": 50,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" State StateFreq\n",
"0 Washington 0.087366\n",
"1 Arizona 0.186446\n",
"2 Nevada 0.096562\n",
"3 California 0.344865\n",
"4 Washington 0.087366\n",
"5 Oregon 0.284760\n",
"6 Oregon 0.284760\n",
"7 Arizona 0.186446\n",
"8 Oregon 0.284760\n",
"9 Oregon 0.284760"
],
"text/html": [
"\n",
"
\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
State
\n",
"
StateFreq
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
Washington
\n",
"
0.087366
\n",
"
\n",
"
\n",
"
1
\n",
"
Arizona
\n",
"
0.186446
\n",
"
\n",
"
\n",
"
2
\n",
"
Nevada
\n",
"
0.096562
\n",
"
\n",
"
\n",
"
3
\n",
"
California
\n",
"
0.344865
\n",
"
\n",
"
\n",
"
4
\n",
"
Washington
\n",
"
0.087366
\n",
"
\n",
"
\n",
"
5
\n",
"
Oregon
\n",
"
0.284760
\n",
"
\n",
"
\n",
"
6
\n",
"
Oregon
\n",
"
0.284760
\n",
"
\n",
"
\n",
"
7
\n",
"
Arizona
\n",
"
0.186446
\n",
"
\n",
"
\n",
"
8
\n",
"
Oregon
\n",
"
0.284760
\n",
"
\n",
"
\n",
"
9
\n",
"
Oregon
\n",
"
0.284760
\n",
"
\n",
" \n",
"
\n",
"
\n",
" \n",
" \n",
" \n",
"\n",
" \n",
"
\n",
"
\n",
" "
]
},
"metadata": {},
"execution_count": 50
}
]
},
{
"cell_type": "markdown",
"source": [
"You could use a transform like this to create a \"frequency encoding\" for a categorical feature.\n",
"\n",
"If you're using training and validation splits, to preserve their independence, it's best to create a grouped feature using only the training set and then join it to the validation set. We can use the validation set's `merge` method after creating a unique set of values with `drop_duplicates` on the training set:"
],
"metadata": {
"id": "8fcZVOZtZ71O"
}
},
{
"cell_type": "code",
"source": [
"# Create splits\n",
"df_train = customer.sample(frac=0.5)\n",
"df_valid = customer.drop(df_train.index)\n",
"\n",
"# Create the average claim amount by coverage type, on the training set\n",
"df_train[\"AverageClaim\"] = df_train.groupby(\"Coverage\")[\"Total Claim Amount\"].transform(\"mean\")\n",
"\n",
"# Merge the values into the validation set\n",
"df_valid = df_valid.merge(\n",
" df_train[[\"Coverage\", \"AverageClaim\"]].drop_duplicates(),\n",
" on=\"Coverage\",\n",
" how=\"left\",\n",
")\n",
"\n",
"df_valid[[\"Coverage\", \"AverageClaim\"]].head(10)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 363
},
"id": "EY5vvGSaZ9aQ",
"outputId": "5981d4ad-b73c-447a-c139-53656709a906"
},
"execution_count": 53,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" Coverage AverageClaim\n",
"0 Basic 381.039827\n",
"1 Premium 653.350814\n",
"2 Basic 381.039827\n",
"3 Basic 381.039827\n",
"4 Basic 381.039827\n",
"5 Basic 381.039827\n",
"6 Premium 653.350814\n",
"7 Basic 381.039827\n",
"8 Extended 489.910983\n",
"9 Basic 381.039827"
],
"text/html": [
"\n",
"